﻿using System.Reflection;
using System.Text;
using EdgeTTS.Net.Models;
using EdgeTTS.Net.Utils;
using Newtonsoft.Json;

namespace EdgeTTS.Net;

public class EdgeTts
{
    /// <summary>
    ///     调试模式
    /// </summary>
    public static bool Debug = false;

    /// <summary>
    ///     同步模式
    /// </summary>
    public static bool Await = false;

    private static string GetGuid()
    {
        return Guid.NewGuid().ToString().Replace("-", "");
    }

    /// <summary>
    ///     讲一个浮点型数值转换为百分比数值
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    private static string FormatPercentage(double input)
    {
        return input.ToString("+#;-#;0") + "%";
    }

    private static string ConvertToAudioFormatWebSocketString(string outputFormat)
    {
        return
            "Content-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n{\"context\":{\"synthesis\":{\"audio\":{\"metadataoptions\":{\"sentenceBoundaryEnabled\":\"false\",\"wordBoundaryEnabled\":\"false\"},\"outputFormat\":\"" +
            outputFormat + "\"}}}}";
    }

    /// <summary>
    /// </summary>
    /// <param name="lang">输出语言</param>
    /// <param name="voice">音源名</param>
    /// <param name="rate">语速，-100% - 100% 之间的值，无需传递百分号</param>
    /// <param name="volume">音量</param>
    /// <param name="text">文本</param>
    /// <returns></returns>
    private static string ConvertToSsmlText(string lang, string voice, int rate, int volume, string? text)
    {
        return
            $"<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis'  xml:lang='{lang}'><voice name='{voice}'><prosody pitch='+0Hz' rate ='{FormatPercentage(rate)}' volume='{volume}'>{text}</prosody></voice></speak>";
    }

    private static string ConvertToSsmlWebSocketString(string requestId, string lang, string voice, int rate,
        int volume, string? msg)
    {
        return
            $"X-RequestId:{requestId}\r\nContent-Type:application/ssml+xml\r\nPath:ssml\r\n\r\n{ConvertToSsmlText(lang, voice, rate, volume, msg)}";
    }

    /// <summary>
    ///     语言转文本，将结果返回到回调函数中
    /// </summary>
    /// <param name="option">播放参数</param>
    /// <param name="voice">音源参数</param>
    /// <param name="callback"></param>
    /// <param name="progress"></param>
    public static void Invoke(PlayOption option, Voice voice, Action<List<byte>>? callback,
        IProgress<List<byte>>? progress = null)
    {
        const string binaryDelim = "Path:audio\r\n";
        var sendRequestId = GetGuid();
        var binary = new List<byte>();
        var isTurnEnd = false;

        var wss = new Wss(
            $"wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4&Sec-MS-GEC={Utilities.GenerateSecMsGecToken()}&Sec-MS-GEC-Version=1-130.0.2849.68");
        wss.OnMessage += (_, e) =>
        {
            if (e.IsText)
            {
                var data = e.Data;
                if (data.Contains("Path:turn.start"))
                {
                    // start of turn, ignore. 开始信号，不用处理
                }
                else if (data.Contains("Path:turn.end"))
                {
                    // 返回内容
                    if (binary.Count > 0)
                        callback?.Invoke(binary);
                    else
                        throw new Exception("返回值为空！");
                    // end of turn, close stream. 结束信号，可主动关闭socket
                    // 音频发送完毕后，最后还会收到一个表示音频结束的文本信息
                    //wss.Close();
                }
                else if (data.Contains("Path:response"))
                {
                    // context response, ignore. 响应信号，无需处理
                }

                // 未知错误，通常不会发生
                if (Debug) Console.WriteLine(e.Data);
                isTurnEnd = true;
            }
            else if (e.IsBinary)
            {
                var data = e.RawData;
                // var requestId = Regex.Match(e.Data, @"X-RequestId:(?<requestId>.*?)\r\n").Groups["requestId"].Value;
                if (data[0] == 0x00 && data[1] == 0x67 && data[2] == 0x58)
                {
                    // Last (empty) audio fragment. 空音频片段，代表音频发送结束
                }
                else
                {
                    var index = Encoding.UTF8.GetString(data).IndexOf(binaryDelim, StringComparison.Ordinal) +
                                binaryDelim.Length;
                    var curVal = data.Skip(index);
                    var collection = curVal as byte[] ?? curVal.ToArray();
                    binary.AddRange(collection);
                    // 传出
                    progress?.Report(collection.ToList());
                }
            }
        };
        wss.OnClose += (_, _) =>
        {
            if (!string.IsNullOrEmpty(option.SavePath)) File.WriteAllBytes(option.SavePath, binary.ToArray());
        };
        wss.OnLog += log =>
        {
            if (Debug) Console.WriteLine($"[{log.LogLevel.ToString()}] {log.Msg}");
        };
        if (wss.Run())
        {
            wss.Send(ConvertToAudioFormatWebSocketString(voice.SuggestedCodec));
            wss.Send(ConvertToSsmlWebSocketString(sendRequestId, voice.Locale, voice.Name, option.Rate,
                (int)option.Volume * 100, option.Text));
        }

        while (Await && !isTurnEnd) Thread.Sleep(10);
    }

    /// <summary>
    ///     另存为mp3文件
    /// </summary>
    /// <param name="option">播放参数</param>
    /// <param name="voice">音源参数</param>
    public static void SaveAudio(PlayOption option, Voice voice)
    {
        if (string.IsNullOrEmpty(option.SavePath)) throw new Exception("保存路径为空，请核对参数后重试.");
        Invoke(option, voice, null);
    }

    /// <summary>
    ///     调用微软Edge接口，文字转语音
    /// </summary>
    /// <param name="option">播放参数</param>
    /// <param name="voice">音源参数</param>
    public static void PlayText(PlayOption option, Voice voice)
    {
        Invoke(option, voice, binary => { _ = Audio.PlayToByteAsync(binary.ToArray(), option.Volume); });
    }

    /// <summary>
    ///     获取一个`AudioPlayer`的对象
    /// </summary>
    /// <param name="option">播放参数</param>
    /// <param name="voice">音源参数</param>
    /// <returns></returns>
    public static AudioPlayer? GetPlayer(PlayOption option, Voice voice)
    {
        AudioPlayer? player = null;
        Invoke(option, voice, binary => { player = new AudioPlayer(binary.ToArray(), option.Volume); });
        while (player == null) Thread.Sleep(10);
        return player;
    }

    #region 异步

    public static async Task PlayTextAsync(PlayOption option, Voice voice,
        CancellationToken cancellationToken = default)
    {
        var url =
            $"wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4&Sec-MS-GEC={Utilities.GenerateSecMsGecToken()}&Sec-MS-GEC-Version=1-130.0.2849.68";
        const string binaryDelim = "Path:audio\r\n";
        var sendRequestId = GetGuid();
        var binary = new List<byte>();

        // 创建一个TaskCompletionSource来控制任务完成
        var tcs = new TaskCompletionSource<bool>();

        var client = new WebSocketClient(url);
        client.Connect();

        client.OnMessageReceived += async (_, e) =>
        {
            try
            {
                if (e.IsText)
                {
                    var data = e.Data;
                    if (data.Contains("Path:turn.start"))
                    {
                        // start of turn, ignore. 开始信号，不用处理
                    }
                    else if (data.Contains("Path:turn.end"))
                    {
                        // 返回内容
                        if (binary.Count > 0)
                        {
                            await Audio.PlayToByteAsync(binary.ToArray(), option.Volume, option.Rate,
                                cancellationToken);
                            // 音频播放完成后，设置任务完成
                            tcs.TrySetResult(true);
                        }
                        else
                        {
                            tcs.TrySetException(new Exception("返回值为空！"));
                        }
                    }
                    else if (data.Contains("Path:response"))
                    {
                        // context response, ignore. 响应信号，无需处理
                    }
                }
                else if (e.IsBinary)
                {
                    var data = e.RawData;
                    if (data[0] == 0x00 && data[1] == 0x67 && data[2] == 0x58)
                    {
                        // Last (empty) audio fragment. 空音频片段，代表音频发送结束
                    }
                    else
                    {
                        var index = Encoding.UTF8.GetString(data).IndexOf(binaryDelim, StringComparison.Ordinal) +
                                    binaryDelim.Length;
                        var curVal = data.Skip(index);
                        var collection = curVal as byte[] ?? curVal.ToArray();
                        binary.AddRange(collection);
                    }
                }
            }
            catch (Exception ex)
            {
                // 如果处理过程中出现任何错误，设置任务失败
                tcs.TrySetException(ex);
            }
        };

        try
        {
            // 设置取消令牌的处理
            cancellationToken.Register(() => tcs.TrySetCanceled());

            // 发送必要的消息
            client.Send(ConvertToAudioFormatWebSocketString(voice.SuggestedCodec));
            client.Send(ConvertToSsmlWebSocketString(sendRequestId, voice.Locale, voice.Name, option.Rate,
                (int)option.Volume * 100, option.Text));

            // 等待任务完成（包括音频播放完成）
            await tcs.Task;
        }
        catch (OperationCanceledException)
        {
            // ignored
        }
    }

    #endregion


    /// <summary>
    ///     获取支持的音频列表
    /// </summary>
    /// <returns></returns>
    public static List<Voice> GetVoice()
    {
        var voiceList = GetEmbedText(Assembly.GetExecutingAssembly().GetName().Name + ".Source.VoiceList.json");
        return JsonConvert.DeserializeObject<List<Voice>>(voiceList)!;
    }

    /// <summary>
    ///     获取嵌入文本资源,程序集.目录名.文件名（而不是\）
    /// </summary>
    /// <param name="res"></param>
    /// <returns></returns>
    private static string GetEmbedText(string res)
    {
        string result;
        var assembly = Assembly.GetExecutingAssembly();
        using var stream = assembly.GetManifestResourceStream(res);
        if (stream != null)
        {
            using var reader = new StreamReader(stream);
            result = reader.ReadToEnd();
        }
        else
        {
            result = string.Empty;
        }

        return result;
    }
}